import os
import xml.etree.ElementTree as ET
from .. import utils
from .. import config
from . import scanner as file_scanner


def gen_arguments(root, header_dirs, include_files, base_arguments, use_relative_path):
    """生成编译参数"""
    arguments = list(base_arguments)  # 从基础参数开始
    for dir in header_dirs:
        arguments.append("-I")
        arguments.append(utils.canonical_path(root, dir, use_relative_path))
    for file in include_files:
        arguments.append(
            "-include" + utils.canonical_path(root, file, use_relative_path)
        )
    return arguments


def gen_compile_commands(
    root, header_dirs, source_files, include_files, base_arguments, use_relative_path
):
    """生成编译命令"""
    for filepath in source_files:
        desc = {
            "directory": root,
            "file": utils.canonical_path(root, filepath, use_relative_path),
        }
        desc["arguments"] = gen_arguments(
            root, header_dirs, include_files, base_arguments, use_relative_path
        )
        yield desc


def make_json_file(
    root,
    header_dirs,
    source_files,
    include_files=None,
    base_arguments=None,
    use_relative_path=True,
):
    """生成 json 文件"""
    # 使用全局变量 compile_commands_json (值为 "compile_commands.json") 来确定文件名
    # 路径应为 root/.vscode/compile_commands.json
    output_filename = config.compile_commands_json  # 引用全局变量
    target_path = os.path.join(root, ".vscode", output_filename)

    # 确保 .vscode 目录存在
    vscode_dir = os.path.dirname(target_path)
    if not os.path.exists(vscode_dir):
        os.makedirs(vscode_dir)

    # 从生成器获取所有编译命令描述并转换为列表
    commands_list = list(
        gen_compile_commands(
            root,
            header_dirs,
            source_files,
            include_files or [],
            base_arguments or ["gcc"],
            use_relative_path,
        )
    )

    # 直接将整个列表写入 JSON 文件，并进行格式化
    utils.write_file_json(target_path, commands_list)


def file_build(root, settings):
    """主函数"""
    wanted_dirs_list = settings.get("wanted_dirs", [])
    hated_dirs_list = settings.get("hated_dirs", [])
    base_arguments = settings.get("keil_base_arguments", config.keil_base_arguments)
    use_relative_path = settings.get("use_relative_path", True)
    header_dirs, source_files = file_scanner.scan_files(
        root, wanted_dirs_list, hated_dirs_list
    )
    file_scanner.cook_header_dirs(root, header_dirs)

    include_files = settings.get("include_files", [])

    make_json_file(
        root,
        header_dirs,
        source_files,
        include_files,
        base_arguments,
        use_relative_path,
    )


def keil_build(project_file, settings):
    """
    解析 Keil uVision 项目文件 (.uvprojx 或 .uvproj) 并生成
    compile_commands.json 文件以供 clangd 或其他工具使用。
    """
    try:
        tree = ET.parse(project_file)
        root = tree.getroot()
    except FileNotFoundError:
        raise Exception(f"项目文件 '{project_file}' 未找到。")
    except ET.ParseError:
        raise Exception(f"解析 XML 文件 '{project_file}' 失败。")

    project_dir = os.path.abspath(os.path.dirname(project_file)).replace("\\", "/")
    # 来自设置的新根路径。如果未提供，则默认为 project_dir。
    root_path = settings.get("root_path")
    if not root_path or not os.path.isdir(root_path):
        root_path = project_dir

    use_relative_path = settings.get("use_relative_path", True)

    # --- 提取全局编译器设置 ---
    compiler_settings = None
    # 首先尝试查找 ARM 编译器设置 (对于 .uvprojx)
    compiler_settings = root.find(
        ".//Target/TargetOption/TargetArmAds/Cads/VariousControls"
    )

    # 如果未找到，尝试查找 C51 编译器设置 (对于 .uvproj)
    if compiler_settings is None:
        compiler_settings = root.find(
            ".//Target/TargetOption/Target51/C51/VariousControls"
        )

    if compiler_settings is None:
        raise Exception("在项目文件中未找到 ARM (Cads) 或 C51 编译器设置。")

    try:
        include_paths_str = compiler_settings.find("IncludePath").text or ""
        defines_str = compiler_settings.find("Define").text or ""
    except AttributeError:
        raise Exception("无法在编译器设置中找到 IncludePath 或 Define。")

    # 格式化包含和定义以用于命令行
    # 处理来自 Keil 项目文件的包含路径。它们是相对于项目文件目录的。
    include_flags = []
    for path in include_paths_str.split(";"):
        if not path:
            continue
        if os.path.isabs(path):
            abs_path = os.path.normpath(path)
        else:
            abs_path = os.path.abspath(os.path.join(project_dir, path))

        processed_path = abs_path.replace("\\", "/")
        if use_relative_path:
            try:
                processed_path = os.path.relpath(processed_path, root_path).replace(
                    "\\", "/"
                )
            except ValueError:
                pass  # 在失败时保留绝对路径 (例如，不同的驱动器)
        include_flags.append(f"-I{processed_path}")

    # 处理来自设置的附加包含路径。假设它们是绝对的或已经相对于 root_path。
    additional_include_flags = []
    for path in settings.get("keil_include_dirs", []):
        processed_path = path.replace("\\", "/")
        if use_relative_path and os.path.isabs(processed_path):
            try:
                processed_path = os.path.relpath(processed_path, root_path).replace(
                    "\\", "/"
                )
            except ValueError:
                pass
        additional_include_flags.append(f"-I{processed_path}")

    # 使用相同的逻辑处理来自设置的包含文件。
    include_files = settings.get("include_files", [])
    include_file_flags = []
    for path in include_files:
        processed_path = path.replace("\\", "/")
        if use_relative_path and os.path.isabs(processed_path):
            try:
                processed_path = os.path.relpath(processed_path, root_path).replace(
                    "\\", "/"
                )
            except ValueError:
                pass
        include_file_flags.append(f"-include{processed_path}")

    define_flags = [
        f"-D{define}" for define in defines_str.replace(",", " ").split() if define
    ]

    compiler_flags = (
        define_flags + include_flags + additional_include_flags + include_file_flags
    )

    # --- 查找所有 C 源文件 ---
    source_files = []
    # 组在 Targets -> Target -> Groups 下
    for group in root.findall(".//Group"):
        for file_element in group.findall(".//File"):
            file_type_element = file_element.find("FileType")
            file_path_element = file_element.find("FilePath")

            # FileType '1' 是 C 源文件，'8' 是 C++ 源文件
            if (
                file_type_element is not None
                and file_type_element.text in ["1", "8"]
                and file_path_element is not None
            ):
                file_path = file_path_element.text
                if file_path:
                    # 项目文件中的路径是相对于项目文件本身的。
                    # 我们需要构造一个绝对路径。
                    abs_file_path = os.path.abspath(
                        os.path.join(project_dir, file_path)
                    )
                    source_files.append(abs_file_path)

    if not source_files:
        print("警告：项目中未找到 C 源文件。")

    # --- 生成编译数据库 ---
    compile_commands = []
    base_arguments = settings.get("keil_base_arguments", config.keil_base_arguments)

    for abs_file_path in source_files:
        # "file" 字段应相对于 root_path
        processed_file_path = abs_file_path.replace("\\", "/")
        if use_relative_path:
            try:
                processed_file_path = os.path.relpath(
                    processed_file_path, root_path
                ).replace("\\", "/")
            except ValueError:
                pass

        # 将基础参数与文件特定的标志组合
        arguments = base_arguments + compiler_flags + [processed_file_path]

        compile_commands.append(
            {
                "directory": root_path,
                "file": processed_file_path,
                "arguments": arguments,
            }
        )

    # --- 写入输出文件 ---
    output_dir = os.path.join(root_path, ".vscode")
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)
    output_path = os.path.join(output_dir, "compile_commands.json")

    with open(output_path, "w", encoding="utf-8") as f:
        utils.write_file_json(output_path, compile_commands)
